package org.iccs.san.repositories.sesame;

import org.openrdf.query.*;
import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLWriter;
import org.openrdf.model.vocabulary.*;
import org.openrdf.repository.*;
import org.openrdf.rio.*;
import org.openrdf.rio.rdfxml.RDFXMLWriter;
import org.openrdf.model.*;

import info.aduna.xml.XMLWriter;

import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.io.*;

import javax.xml.datatype.XMLGregorianCalendar;

public class SesameGraph {

	Repository therepository = null;

	// useful -local- constants
	public static RDFFormat NTRIPLES = RDFFormat.NTRIPLES;
	public static RDFFormat N3 = RDFFormat.N3;
	public static RDFFormat RDFXML = RDFFormat.RDFXML;
	public static String RDFTYPE = RDF.TYPE.toString();

	private int conns = 0;

	// private RepositoryConnection rocon = null;

	public SesameGraph(Repository repository) {
		this.therepository = repository;
		/*
		 * try { this.rocon = this.therepository.getConnection(); } catch
		 * (RepositoryException e) { // TODO Auto-generated catch block
		 * e.printStackTrace(); }
		 */
	}

	public Repository getRepository() {
		return this.therepository;
	}

	public void setNamespace(String prefix, String name) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				con.setNamespace(prefix, name);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Literal factory
	 * 
	 * @param s
	 *            the literal value
	 * @param typeuri
	 *            uri representing the type (generally xsd)
	 * @return
	 */
	public org.openrdf.model.Literal Literal(String s, URI typeuri) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				ValueFactory vf = con.getValueFactory();
				if (typeuri == null) {
					return vf.createLiteral(s);
				} else {
					return vf.createLiteral(s, typeuri);
				}
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Untyped Literal factory
	 * 
	 * @param s
	 *            the literal
	 * @return
	 */
	public org.openrdf.model.Literal Literal(String s) {
		return Literal(s, null);
	}

	public org.openrdf.model.Literal Literal(XMLGregorianCalendar calendar) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				ValueFactory vf = con.getValueFactory();

				return vf.createLiteral(calendar);

			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * URIref factory
	 * 
	 * @param uri
	 * @return
	 */
	public URI URIref(String uri) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				ValueFactory vf = con.getValueFactory();
				return vf.createURI(uri);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * BNode factory
	 * 
	 * @return
	 */
	public BNode bnode() {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				ValueFactory vf = con.getValueFactory();
				return vf.createBNode();
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * dump RDF graph
	 * 
	 * @param out
	 *            output stream for the serialization
	 * @param outform
	 *            the RDF serialization format for the dump
	 * @return
	 */
	public void dumpRDF(OutputStream out, RDFFormat outform) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				RDFWriter w = Rio.createWriter(outform, out);
				con.export(w, new Resource[0]);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Convenience URI import for RDF/XML sources
	 * 
	 * @param urlstring
	 *            absolute URI of the data source
	 */
	public void addURI(String urlstring) {
		addURI(urlstring, RDFFormat.RDFXML);
	}

	/**
	 * Import data from URI source Request is made with proper HTTP ACCEPT
	 * header and will follow redirects for proper LOD source negotiation
	 * 
	 * @param urlstring
	 *            absolute URI of the data source
	 * @param format
	 *            RDF format to request/parse from data source
	 */
	public void addURI(String urlstring, RDFFormat format) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				URL url = new URL(urlstring);
				URLConnection uricon = (URLConnection) url.openConnection();
				uricon
						.addRequestProperty("accept", format
								.getDefaultMIMEType());
				InputStream instream = uricon.getInputStream();
				con.add(instream, urlstring, format, new Resource[0]);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Import RDF data from a string
	 * 
	 * @param rdfstring
	 *            string with RDF data
	 * @param format
	 *            RDF format of the string (used to select parser)
	 */
	public void addString(String rdfstring, RDFFormat format) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				StringReader sr = new StringReader(rdfstring);
				con.add(sr, "", format, new Resource[0]);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Import RDF data from a file
	 * 
	 * @param location
	 *            of file (/path/file) with RDF data
	 * @param format
	 *            RDF format of the string (used to select parser)
	 */
	public void addFile(String filepath, RDFFormat format) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				con.add(new File(filepath), "", format, new Resource[0]);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Import RDF data from a file
	 * 
	 * @param location
	 *            of file (/path/file) with RDF data
	 * @param format
	 *            RDF format of the string (used to select parser)
	 * @param baseURI
	 *            The base URI to resolve any relative URIs that are in the data
	 *            against. This defaults to the value of file.toURI() if the
	 *            value is set to null.
	 * @param context
	 *            The Sesame context
	 */
	public void addFile(String filepath, RDFFormat format, String baseURI) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				// con.add(new File(filepath), baseURI, format, new
				// Resource[0]);
				ValueFactory vf = con.getValueFactory();
				URI context = vf.createURI(filepath);
				con.add(new File(filepath), baseURI, format, context);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Insert Triple/Statement into graph
	 * 
	 * @param s
	 *            subject uriref
	 * @param p
	 *            predicate uriref
	 * @param o
	 *            value object (URIref or Literal)
	 */
	public void add(URI s, URI p, Value o) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				ValueFactory myFactory = con.getValueFactory();
				Statement st = myFactory.createStatement((Resource) s, p,
						(Value) o);
				con.add(st, new Resource[0]);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			// handle exception
		}
	}

	/**
	 * Delete Triple/Statement from graph
	 * 
	 * @param s
	 *            subject uriref
	 * @param p
	 *            predicate uriref
	 * @param o
	 *            value object (URIref or Literal)
	 */
	public void remove(URI s, URI p, Value o) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				ValueFactory myFactory = con.getValueFactory();
				Statement st = myFactory.createStatement((Resource) s, p,
						(Value) o);
				con.remove(st, new Resource[0]);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			// handle exception
		}
	}

	/**
	 * Tuple pattern query - find all statements with the pattern, where null is
	 * a wild card
	 * 
	 * @param s
	 *            subject (null for wildcard)
	 * @param p
	 *            predicate (null for wildcard)
	 * @param o
	 *            object (null for wildcard)
	 * @return serialized graph of results
	 */
	@SuppressWarnings("unchecked")
	public List tuplePattern(URI s, URI p, Value o) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				RepositoryResult repres = con.getStatements(s, p, o, true,
						new Resource[0]);
				ArrayList reslist = new ArrayList();
				while (repres.hasNext()) {
					reslist.add(repres.next());
				}
				return reslist;
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Execute a CONSTRUCT/DESCRIBE SPARQL query against the graph
	 * 
	 * @param qs
	 *            CONSTRUCT or DESCRIBE SPARQL query
	 * @param format
	 *            the serialization format for the returned graph
	 * @return serialized graph of results
	 */
	public String[] runSPARQL(String qs, RDFFormat format) {
		String[] ret = new String[2];
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				GraphQuery query = con.prepareGraphQuery(
						org.openrdf.query.QueryLanguage.SPARQL, qs);
				StringWriter stringout = new StringWriter();
				RDFWriter w = Rio.createWriter(format, stringout);
				query.evaluate(w);
				ret[0] = "OK";
				ret[1] = stringout.toString();
				return ret;
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println("\nSPARQL=>\n" + qs + "\n");
			ret[0] = "ERROR";
			ret[1] = e.toString();
		}
		return ret;
	}

	/**
	 * Execute a SELECT SPARQL query against the graph
	 * 
	 * @param qs
	 *            SELECT SPARQL query
	 * @return list of solutions, each containing a hashmap of bindings
	 */
	@SuppressWarnings("unchecked")
	public List runSPARQL(String qs) {
		try {
			/*
			 * new code: try to cache the connection for readonly queries
			 * RepositoryConnection con; if (this.rocon==null) { this.rocon =
			 * this.therepository.getConnection(); } con = rocon;
			 */
			// old code :
			RepositoryConnection con = therepository.getConnection();

			try {
				// System.err.println("\nSPARQL:\n" + qs + "\n");
				TupleQuery query = con.prepareTupleQuery(
						org.openrdf.query.QueryLanguage.SPARQL, qs);
				TupleQueryResult qres = query.evaluate();
				ArrayList reslist = new ArrayList();
				while (qres.hasNext()) {
					BindingSet b = qres.next();
					Set names = b.getBindingNames();
					HashMap hm = new HashMap();
					for (Object n : names) {
						hm.put((String) n, b.getValue((String) n));
					}
					reslist.add(hm);
				}
				return reslist;
			} finally {
				// new code (don't close the connection):
				con.close();
				// System.err.println("**deactivated connection close " +
				// therepository.getDataDir().getAbsolutePath());
			}
		} catch (Exception e) {
			// e.printStackTrace();
			System.err.println("\nERROR in SPARQL =>\n" + qs + "\n");
			System.err.println("\nSPARQL ERROR =>\n" + e + "\n");
		}
		return null;
	}

	/**
	 * Execute a SELECT SPARQL query against the graph
	 * 
	 * @param qs
	 *            SELECT SPARQL query
	 * @return a string 0:Status 1:message
	 */
	public String[] runSPARQLasXML(String qs) {
		String[] ret = new String[2];
		ret[0] = "ERROR";
		try {
			RepositoryConnection con = therepository.getConnection();
			Writer strWriter = new java.io.StringWriter();
			XMLWriter xmlWriter = new XMLWriter(strWriter);
			SPARQLResultsXMLWriter sparqlWriter = new SPARQLResultsXMLWriter(
					xmlWriter);
			try {
				TupleQuery query = con.prepareTupleQuery(
						org.openrdf.query.QueryLanguage.SPARQL, qs);
				query.evaluate(sparqlWriter);
				ret[0] = "OK";
				ret[1] = strWriter.toString();
				return ret;
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println("\nSPARQL=>\n" + qs.replaceAll(" .", " .\n")
					+ "\n");
			ret[1] = e.toString();
		}
		return ret;
	}

	/**
	 * Copies a uri including all (asserted) properties
	 * 
	 * @param from_uri
	 * @param to_uri
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Boolean createCopy(URI from_uri, URI to_uri) {
		try {
			RepositoryConnection con = therepository.getConnection();
			// BNode to_uri = null;
			try {
				RepositoryResult<Statement> statements = con.getStatements(
						from_uri, null, null, false);
				// ValueFactory vf = con.getValueFactory();
				// to_uri = vf.createBNode();
				try {
					while (statements.hasNext()) {
						Statement st = statements.next();
						con.add(to_uri, st.getPredicate(), st.getObject());
					}
				} finally {
					// make sure the result object is closed
					// properly
					statements.close();
				}
			} finally {
				con.close();
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public Boolean replaceObject(URI s, URI p, URI o) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				// delete all instances of s,p,*
				con.remove((Resource) s, p, null);
				// add statement s,p,o
				con.add((Resource) s, p, (Value) o);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public Boolean replaceObject(URI s, URI p, Literal o) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				// delete all instances of s,p,*
				con.remove((Resource) s, p, null);
				// add statement s,p,o
				con.add((Resource) s, p, (Value) o);
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public String getAssertedType(String uri) {
		String result = null;

		try {
			// delete all statements where uri is subject
			RepositoryConnection con = therepository.getConnection();
			RepositoryResult<Statement> statements = con
					.getStatements(this.URIref(uri), this
							.URIref(SesameGraph.RDFTYPE), null, false);
			try {

				// We return only the first statement !
				if (statements.hasNext()) {
					Statement st = statements.next();
					result = st.getObject().stringValue();
				}
			} finally {
				// make sure the result object is closed
				// properly
				statements.close();
				con.close();
			}

		} catch (Exception e) {
			System.err.println(e);

		}
		/*
		 * finally { try { this.getRepository().getConnection().close(); } catch
		 * (RepositoryException e) { // TODO Auto-generated catch block
		 * e.printStackTrace(); } }
		 */

		return result;
	}

	public Boolean isInstanceOf(String individualURI, String classURI,
			Boolean include_inferred) {
		Boolean result = false;
		try {
			RepositoryConnection con = therepository.getConnection();
			RepositoryResult<Statement> statements = con.getStatements(
					URIref(individualURI), URIref(SesameGraph.RDFTYPE),
					URIref(classURI), include_inferred);
			try {
				// We return only the first statement !
				if (statements.hasNext()) {
					result = true;
				} else
					result = false;
			} finally {
				// make sure the result object is closed
				// properly
				statements.close();
				con.close();
			}

		} catch (Exception e) {
			System.err.println(e);

		}
		return result;
	}

	public boolean isSubClassOf(String subclass, String superclass,
			Boolean include_inferred) {
		Boolean result = false;
		try {
			// delete all statements where uri is subject
			RepositoryConnection con = therepository.getConnection();

			RepositoryResult<Statement> statements = con.getStatements(
					URIref(subclass),
					URIref("http://www.w3.org/2000/01/rdf-schema#subClassOf"),
					URIref(superclass), include_inferred);
			try {
				// We return only the first statement !
				if (statements.hasNext()) {
					result = true;
				} else
					result = false;
			} finally {
				// make sure the result object is closed
				// properly
				statements.close();
				con.close();
			}

		} catch (Exception e) {
			System.err.println(e);

		}
		return result;
	}

	public RepositoryResult<Resource> getContextIDs() {
		RepositoryResult<Resource> r = null;
		try {
			RepositoryConnection con = therepository.getConnection();

			try {
				r = con.getContextIDs();

			} finally {
				con.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		return r;
	}

	public Boolean removeContext(Resource context) {
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				System.err.println("removing context : " + context.toString());
				con.clear(context);
				System.err.println("removed context : " + context.toString());
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public String exportContextToRDFXML(Resource context) {
		String result = null;
		try {
			RepositoryConnection con = therepository.getConnection();
			try {
				// Export all statements in the context to System.out, in
				// RDF/XML format

				OutputStream output = new OutputStream() {
					private StringBuilder string = new StringBuilder();

					@Override
					public void write(int b) throws IOException {
						this.string.append((char) b);
					}

					// Netbeans IDE automatically overrides this toString()
					public String toString() {
						return this.string.toString();
					}
				};

				RDFHandler rdfxmlWriter = new RDFXMLWriter(output);
				con.export(rdfxmlWriter, context);
				result = output.toString();

				// add statement s,p,o
			} finally {
				con.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

}
